using web
using fandoc
using js_markup


const class PodBrowserMod : HtmlMod {

  const Str:Obj tabAttr := ["border":1,"cellspacing":0]

  override Void build(HtmlBuilder page) {
    query := req.uri.query
    if (query.size==0) query = ["allPods":""]
    page.with {
      HTML {
        HEAD { TITLE("PodBrowser") }
        BODY {
          query.each |value,key| {
            switch (key) {
            case "allPods": showAllPods(page)
            case "pod": showPod(page,value)
            case "type": showType(page,value)
            case "slot": showSlot(page,value)
            case "fandoc": showFanDoc(page,value)
            default: showError(page,"Unknown key: $key")
            }
          }
        }
      }
    }
  }

  Void showAllPods(HtmlBuilder page) {
    page.H(1) { "All Pods", }
    fieldsToArray (page, Pod.list, ["Name":|p,o|{podLink(p,o)},"Version":"version"])
  }

  Void showPod(HtmlBuilder page, Str name) {
    page.H(1) { "Pod: $name", }
    pod := Pod.find(name)
    if (pod==null) {
      page.P { "Not an existing pod.", }
    } else page.with {
      TABLE { ["border":1,"cellspacing":0],;
        TR2(page,"Version:") { pod.version.toStr, }
        TR2(page,"Uri:") { pod.uri.toStr, }
        TR2(page,"Depends:") {
          pod.depends.each |dep| {
            page.A(Uri("?pod=$dep.name")) { dep.toStr, }
          }
        }
      }
      H(2) {"Types",}
      if (!pod.types.isEmpty) {
        UL {
          pod.types.each |t| {
            if (!t.isSynthetic) page.LI { typeLink(page,t,t.name) }
          }
        }
      }
      H(2) { "Meta", }
      if (!pod.meta.isEmpty) {
        UL {
          pod.meta.each |v,k| { page.LI { "$k = $v", } }
        }
      }
      H(2) {"Files",}
      if (!pod.files.isEmpty) {
        UL {
          pod.files.each |f| {
            page.LI {
              switch (f.ext) {
                case "fandoc": fileLink(page,f)
                default: tx(f)
              }
            }
          }
        }
      }
      HR
      if(pod.doc!=null) parseDoc(pod.doc.in,page)
    }
  }

  Void showType(HtmlBuilder page, Str name) {
    page.H(1) { "Type: $name", }
    type := Type.find(name,false)
    if (type==null) {
      page.P { "Note an existing type.", }
      return
    }
    page.with {
      P {
        CODE {
          type.facets.each |fac| { page.tx("@"+fac); page.BR }
          if (type.isPublic) tx("public ")
          if (type.isInternal) tx("internal ")
          if (type.isAbstract) tx("abstract ")
          if (type.isFinal) tx("final ")
          if (type.isConst) tx("const ")
          if (type.isClass) tx("class ")
          if (type.isFacet) tx("facet ")
          if (type.isEnum) tx("enum ")
          if (type.isMixin) tx("mixin ")
          tx(type.signature)
          parents := type.mixins.dup
          if (type.base!=null && type.base!=Obj#) parents.insert(0,type.base)
          if (parents.size>0) { tx(" : "+parents.join(",")) }
        }
        if (type.isGeneric) tx(" (generic)")
        if (type.isSynthetic) tx(" (synthetic)")
        if (type.isVal) tx("(value)")
      }
      TABLE { ["border":1,"cellspacing":0],;
        TR2(page,"Pod:") { podLink(page,type.pod) }
        TR2(page,"Base:") {
          if(type.base!=null) { typeLink(page,type.base) }
        }
        TR2(page,"Mixins:") {
          type.mixins.each { typeLink(page,it) }
        }
        TR2(page,"Params:") { type.params.join(" "), }
        TR2(page,"Inheritance: ") {
          type.inheritance.each |t| { typeLink(page,t) }
        }
      }
      H(2) { "Slots", }
      TABLE { tabAttr,;
        TR {
          TH { "Name", }; TH { "Signature", }; TH { "Defining Type", }
        }
        type.slots.exclude{it.isPrivate||it.isSynthetic}.sort|x,y|{x.name<=>y.name}.each |slot| {
          page.TR {
            TD { slotLink(page,slot,slot.name) }
            if (slot.parent==type) {
              TD { STRONG { CODE { slot.signature,} } }
            } else {
              TD { CODE { slot.signature,} }
            }
            TD { typeLink(page,slot.parent) }
          }
        }
      }
      HR
      if(type.doc!=null) parseDoc(type.doc.in,page)
    }
  }

  Void showSlot(HtmlBuilder page, Str name) {
    slot := Slot.find(name,false)
    if (slot==null) {
      page.H(1) {"Slot: $name",}
      page.P {"Not an existing slot.",}
      return
    }
    field := slot.isField ? (Field)slot : null
    method := slot.isMethod ? (Method)slot : null
    page {
      H(1) { (slot.isMethod?"Method: ":"Field: ")+name, }
      P {
        CODE {
          if (slot.isAbstract) tx("abstract ")
          if (slot.isPublic) tx("public ")
          if (slot.isPrivate) tx("private ")
          if (slot.isInternal) tx("internal ")
          if (slot.isProtected) tx("protected ")
          if (slot.isStatic) tx("static ")
          if (slot.isConst) tx("const ")
          if (slot.isCtor) tx("new ")
          if (slot.isNative) tx("native ")
          if (slot.isOverride) tx("override ")
          tx(slot.signature)
          if (slot.isSynthetic) tx(" (synthetic)")
        }
      }
      TABLE { ["border":1,"cellspacing":0],;
        TR2(page,"Parent:") { typeLink(page,slot.parent) }
        TR2(page,"Facets:") { slot.facets.join(" "), }
        if (field!=null) TR2(page,"Type:") { typeLink(page,field.type) }
        if (method!=null) TR2(page,"Returns:") { typeLink(page,method.returns) }
      }
      if (method!=null) {
        H(2) { "Parameters", }
        TABLE { tabAttr,;
          TR {
            TH { "Name", }; TH { "Type", }; TH { "Default", }
          }
          method.params.each |par| {
            page.TR {
              TD { CODE; tx(par.name); CODE }
              TD { typeLink(page,par.type) }
              TD { tx(par.hasDefault) }
            }
          }
        }
      }
      HR
      if(slot.doc!=null) parseDoc(slot.doc.in,page)
    }
  }

  Void showFanDoc(HtmlBuilder page, Str name) {
   file := (File)Uri(name).get
   parseDoc(file.in,page)
  }

  Void fieldsToArray(HtmlBuilder page, Obj[] list, Str:Obj fields) {
    page.TABLE { ["border":1,"cellspacing":0],;
      page.TR {
        fields.each |v,k| {
          page.TH { k, }
        }
      }
      list.each |obj| {
        page.TR {
          fields.each |v,k| {
            page.TD {
              if (v is |XhtmlBuilder,Obj|) {
                ((|XhtmlBuilder,Obj|)v).call(page,obj)
              } else {
                add(obj.trap(v,null).toStr)
              }
            }
          }
        }
      }
    }
  }

  Void parseDoc (InStream in, HtmlBuilder page) {
    doc := FandocParser.make.parse("Doc",in)
    parseDocSub(doc,page)
  }

  Void parseDocSub (DocNode node, HtmlBuilder page) {
    if (node is DocElem) {
        page.elem(((DocElem)node).htmlName) {
          ((DocElem)node).children.each |child| { parseDocSub(child,page) }
        }
    } else if (node is DocText) {
      page.tx(((DocText)node).str)
    } else {
      page.tx(node.toStr)
    }
  }

  Void showError(HtmlBuilder page, Str text) {
    page.H(1) {"PodBrowser Error",}
    page.P { text, }
  }

  Void podLink(HtmlBuilder page, Pod p, Str? text:=null) {
    page.A(Uri("?pod=$p.name")) { CODE { tx(text ?: p.toStr) } }
  }

  Void typeLink(HtmlBuilder page, Type t, Str? text:=null) {
    page.A(Uri("?type=$t")) { CODE { tx(text ?: t.toStr) } }
  }

  Void slotLink(HtmlBuilder page, Slot s, Str? text:=null) {
    page.A(Uri("?slot=$s")) { CODE { tx(text ?: s) } }
  }

  Void fileLink(HtmlBuilder page, File s, Str? text:=null) {
    page.A(Uri("?fandoc=$s")) { tx(text ?: s) }
  }


  Void ULfromList(HtmlBuilder page, List? list, |Obj->Str|? meth := null) {
    if (list==null || list.isEmpty) return
    page.UL {
      list.each |obj| { page.LI { meth==null ? obj.toStr : meth(obj), } }
    }
  }

  Void TR2(HtmlBuilder page, Obj text, |HtmlBuilder| func) {
    page.TR {
      TD {tx(text)}
      TD {func(it)}
    }
  }


}


